﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEngine.Networking;
using hotupdate.cframework;
using wnity.zframework;

public abstract class BaseHotUpdate : MonoBehaviour
{
    protected enum TaskType
    {
        check_white_device,
        code_check,
        code_download,
        code_load,
        addressable_check,
        addressable_download,
        addressable_catalog_reload,
    }

    protected enum TaskStatus
    {
        none,
        doing,
        failed,
        done,
    }


    

    protected abstract class HotUpdateTask
    {
        public TaskType taskType;
        public TaskStatus status = TaskStatus.doing;

        public HotUpdateTask(TaskType _taskType)
        {
            this.taskType = _taskType;
        }

        
        public virtual IEnumerator Run() { yield return null; }

        public virtual float GetProgress(out long total, out long current)
        {
            total = 0;
            current = 0;
            return -1;
        }

        public static int ToTaskKey(TaskType taskType, TaskStatus status)
        {
            return (int)taskType * 100 + (int)status;
        }

        public int ToKey()
        {
            return ToTaskKey(this.taskType, this.status);
        }
    }


    protected class GetWhiteListTask: HotUpdateTask
    {
        string whiteListDeviceId;
        public GetWhiteListTask(string _whiteListDeviceId)
            :base(TaskType.check_white_device)
        {
            whiteListDeviceId = _whiteListDeviceId;
        }

        public override IEnumerator Run()
        {
            if(WhiteDeviceMgr.GetStatus() == WhiteDeviceMgr.Status.finish)
            {
                yield return true;
            }

            yield return WhiteDeviceMgr.Check(AppManager.GetAppInfo().hotupdate_url, whiteListDeviceId).WithResult();
            yield return WhiteDeviceMgr.GetStatus() == WhiteDeviceMgr.Status.finish;
        }
    }


    protected class CodeCheckTask: HotUpdateTask
    {
        public CodeCheckTask()
            :base(TaskType.code_check)
        {

        }


        public override IEnumerator Run()
        {
            while(true)
            {
                switch (HotUpdateDllMgr.GetStatus())
                {
                    case HotUpdateDllMgr.Status.none:
                    case HotUpdateDllMgr.Status.init_failed:
                        {
                            yield return HotUpdateDllMgr.init(AppManager.GetAppInfo().hotupdate_url).WithResult();
                            
                            if (HotUpdateDllMgr.GetStatus() == HotUpdateDllMgr.Status.init_failed)
                            {
                                yield return false;
                            }
                        }
                        break;

                    case HotUpdateDllMgr.Status.initing:
                        break;

                    case HotUpdateDllMgr.Status.inited:
                    case HotUpdateDllMgr.Status.version_check_failed:
                        {
                            yield return HotUpdateDllMgr.CheckLatestVersion().WithResult();
                            
                            if (HotUpdateDllMgr.GetStatus() == HotUpdateDllMgr.Status.version_check_failed)
                            {
                                yield return false;
                            }
                        }
                        break;

                    case HotUpdateDllMgr.Status.version_checked:
                        {
                            yield return true;
                        }
                        break;
                }
            }
        }
    }


    protected class CodeDownloadTask: HotUpdateTask
    {
        public CodeDownloadTask()
            :base(TaskType.code_download)
        {

        }

        public override IEnumerator Run()
        {
            while(true)
            {
                switch (HotUpdateDllMgr.GetStatus())
                {
                    case HotUpdateDllMgr.Status.version_checked:
                    case HotUpdateDllMgr.Status.bundle_download_failed:
                        {
                            yield return HotUpdateDllMgr.Download().WithResult();
                            if (HotUpdateDllMgr.GetStatus() == HotUpdateDllMgr.Status.bundle_download_failed)
                            {
                                yield return false;
                            }
                        }
                        break;

                    case HotUpdateDllMgr.Status.bundle_downladed:
                        {
                            yield return true;
                        }
                        break;

                }
            }
        }


        public override float GetProgress(out long total, out long current)
        {
            int totalSize = 0;
            int progress = 0;
            if(this.status == TaskStatus.doing)
            {
                if(HotUpdateDllMgr.GetDownloadProgress(out totalSize, out progress))
                {
                    total = totalSize;
                    current = progress;
                    return progress / (float)totalSize;
                }
                else
                {
                    total = 0;
                    current = 0;
                    return 0;
                }
            }
            else
            {
                total = 0;
                current = 0;
                return -1;
            }
        }
    }




    protected class CodeLoadTask: HotUpdateTask
    {
        public static LoadDllConfig loadDllConfig;

        public CodeLoadTask()
            :base(TaskType.code_load)
        {

        }
        

        public static IEnumerator StartGame()
        {
            var ret = CommonUtil.CallMethodWithResult(loadDllConfig.startGameDllNames[loadDllConfig.startGameDllNames.Length - 1].Replace(".dll", ""), loadDllConfig.startGameClassName, loadDllConfig.startGameMethodName, new object[] { loadDllConfig.gameScene });
            var task = ((IEnumerator)ret.Item2).WithResult();
            yield return task;
            yield return task.Current;
        }


        public override IEnumerator Run()
        {
            while(true)
            {
                switch (HotUpdateDllMgr.GetStatus())
                {
                    case HotUpdateDllMgr.Status.bundle_downladed:
                    case HotUpdateDllMgr.Status.code_load_failed:
                        {
                            var task = HotUpdateDllMgr.LoadBundle().WithResult();
                            yield return task;

                            var result = (bool)task.Current;
                            
                            if (result)
                            {
                                var configAsset = HotUpdateDllMgr.GetBundle().LoadAsset<TextAsset>(LoadDllConfig.fileName);
                                if (configAsset == null)
                                {
                                    yield return false;
                                }

                                var flc = Newtonsoft.Json.JsonConvert.DeserializeObject<LoadDllConfig>(configAsset.text);
                                if (flc == null)
                                {
                                    yield return false;
                                }

                                loadDllConfig = flc;

                                 var task2 = HotUpdateDllMgr.LoadCode(flc.startGameDllNames, true).WithResult();
                                yield return task2;
                                var a = task2.Current;
                                    
                                HotUpdateDllMgr.UnloadBundle();

                                if (HotUpdateDllMgr.GetStatus() == HotUpdateDllMgr.Status.code_load_failed)
                                {
                                    yield return false;
                                }
                            }
                            else
                            {
                                yield return false;
                            }
                            
                        }
                        break;

                    case HotUpdateDllMgr.Status.code_loaded:
                        {
                            yield return true;
                        }
                        break;
                }
            }
        }
    }

    
    protected class AddressableCheckTask: HotUpdateTask
    {
        public AddressableCheckTask()
            :base(TaskType.addressable_check)
        {
        }

        public override IEnumerator Run()
        {
            while(true)
            {
                switch (HotUpdateAddressableMgr.GetStatus())
                {
                    case HotUpdateAddressableMgr.Status.none:
                    case HotUpdateAddressableMgr.Status.init_failed:
                        {
                            int version = Mathf.Max(AppManager.GetAppInfo().hotupdate_version, HotUpdateDllMgr.GetLatestVersion());
                            var task = HotUpdateAddressableMgr.init(AppManager.GetAppInfo().hotupdate_url, version).WithResult();
                            yield return task;
                            var result = task.Current;

                            if (HotUpdateAddressableMgr.GetStatus() == HotUpdateAddressableMgr.Status.init_failed)
                            {
                                yield return false;
                            }
                        }
                        break;

                    case HotUpdateAddressableMgr.Status.inited:
                    case HotUpdateAddressableMgr.Status.version_check_failed:
                        {
                            yield return HotUpdateAddressableMgr.CheckLatestVersion().WithResult();
                            if (HotUpdateAddressableMgr.GetStatus() == HotUpdateAddressableMgr.Status.version_check_failed)
                            {                                    
                                yield return false;
                            }
                        }
                        break;

                    case HotUpdateAddressableMgr.Status.version_checked:
                    case HotUpdateAddressableMgr.Status.catalog_download_failed:
                        {
                            yield return HotUpdateAddressableMgr.DownloadCatalog().WithResult();
                            if (HotUpdateAddressableMgr.GetStatus() == HotUpdateAddressableMgr.Status.catalog_download_failed)
                            {
                                yield return false;
                            }
                        }
                        break;

                    case HotUpdateAddressableMgr.Status.catalog_downloaded:
                    case HotUpdateAddressableMgr.Status.catalog_update_failed:
                        {
                            yield return HotUpdateAddressableMgr.Update().WithResult();
                            if (HotUpdateAddressableMgr.GetStatus() == HotUpdateAddressableMgr.Status.catalog_update_failed)
                            {
                                yield return false;
                            }
                        }
                        break;

                    case HotUpdateAddressableMgr.Status.catalog_updated:
                    case HotUpdateAddressableMgr.Status.downloaded:
                        {
                            yield return true;
                        }
                        break;
                }
            }
        }
    }

    
    protected class AddressableDownloadTask: HotUpdateTask
    {
        public AddressableDownloadTask()
            :base(TaskType.addressable_download)
        {
        }


        public override IEnumerator Run()
        {
            while(true)
            {
                switch (HotUpdateAddressableMgr.GetStatus())
                {
                    case HotUpdateAddressableMgr.Status.catalog_updated:
                    case HotUpdateAddressableMgr.Status.download_failed:
                        {
                            yield return HotUpdateAddressableMgr.Download().WithResult();
                            if (HotUpdateAddressableMgr.GetStatus() == HotUpdateAddressableMgr.Status.download_failed)
                            {
                                yield return false;
                            }
                        }
                        break;

                    case HotUpdateAddressableMgr.Status.downloaded:
                        {
                            yield return true;
                        }
                        break;
                }
            }
        }

        public override float GetProgress(out long total, out long current)
        {
            if(this.status == TaskStatus.doing)
            {
                HotUpdateAddressableMgr.GetProgress(out total, out current);
                return HotUpdateAddressableMgr.GetPercentComplete();
            }
            else
            {
                total = 0;
                current = 0;
                return -1;
            }
        }
    }

    protected class AddressableCatalogReloadTask: HotUpdateTask
    {
        public AddressableCatalogReloadTask()
            :base(TaskType.addressable_catalog_reload)
        {
        }


        public override IEnumerator Run()
        {
            var task = HotUpdateAddressableMgr.ReloadCatalog().WithResult();
            yield return task;
            var result = task.Current;
            yield return result;
        }
    }

    public bool useOldLogic;

    
    
    protected int totalProgress = 0;
    protected System.Collections.Hashtable statusProgressMap = new System.Collections.Hashtable();
    protected System.Collections.Hashtable sumProgressMap = new System.Collections.Hashtable();

    
    protected System.Collections.Hashtable statusDownloadSpeedMap = new System.Collections.Hashtable();
    protected System.Collections.Hashtable statusDownloadSpeedCheckLastTimeMap = new System.Collections.Hashtable();
    protected System.Collections.Hashtable statusDownloadProgressCheckLastTimeMap = new System.Collections.Hashtable();
    ArrayList hotupdateTasks = new ArrayList();
    int curTaskIndex = -1;
    
    protected HotUpdateTask GetCurTask(bool returnLast = false)
    {
        if(curTaskIndex >= 0 && curTaskIndex < hotupdateTasks.Count)
        {
            return hotupdateTasks[curTaskIndex] as HotUpdateTask;
        }

        if(returnLast && curTaskIndex >= hotupdateTasks.Count)
        {
            if(hotupdateTasks.Count > 0)
            {
                return hotupdateTasks[hotupdateTasks.Count - 1] as HotUpdateTask;
            }
        }

        return null;
    }

    protected virtual void InitStatusProgressWeight()
    {
        statusProgressMap.Add(TaskType.check_white_device, 10);
        statusProgressMap.Add(TaskType.code_check, 10);
        statusProgressMap.Add(TaskType.code_download, 30);
        statusProgressMap.Add(TaskType.code_load, 10);
        statusProgressMap.Add(TaskType.addressable_check, 10);
        statusProgressMap.Add(TaskType.addressable_download, 100);
        statusProgressMap.Add(TaskType.addressable_catalog_reload, 10);
    }

    protected virtual string GetHotUpdateDeviceId()
    {
        string deviceId = PlayerPrefs.GetString("HotUpdateDeviceId");
        if (string.IsNullOrEmpty(deviceId))
        {
            deviceId = System.DateTime.Now.ToString("yyyy-MM-dd_HH:mm:ss:fff") + "_" + Time.realtimeSinceStartup;
            PlayerPrefs.SetString("HotUpdateDeviceId", deviceId);
        }
        return deviceId;
    }

    protected void InitHotUpdateInfo()
    {
        HotUpdateDllMgr.loadMetadataForAOTAssemblyHandler = LoadMetadataForAOTAssembly;

        sumProgressMap.Clear();
        statusProgressMap.Clear();
        statusDownloadSpeedMap.Clear();
        statusDownloadSpeedCheckLastTimeMap.Clear();
        statusDownloadProgressCheckLastTimeMap.Clear();
        
        
    


        curTaskIndex = 0;
        hotupdateTasks.Clear();
        hotupdateTasks.Add(new GetWhiteListTask(GetHotUpdateDeviceId()));
        hotupdateTasks.Add(new CodeCheckTask());
        if(useOldLogic)
        {
            hotupdateTasks.Add(new CodeDownloadTask());
            hotupdateTasks.Add(new CodeLoadTask());
        }
        hotupdateTasks.Add(new AddressableCheckTask());
        hotupdateTasks.Add(new AddressableDownloadTask());

        if(!useOldLogic)
        {
            hotupdateTasks.Add(new CodeDownloadTask());
            hotupdateTasks.Add(new CodeLoadTask());
            hotupdateTasks.Add(new AddressableCatalogReloadTask());
        }

        totalProgress = 0;
        InitStatusProgressWeight();
        for(int i = 0; i < hotupdateTasks.Count; i++)
        {
            var task = hotupdateTasks[i] as HotUpdateTask;
            
            sumProgressMap.Add(HotUpdateTask.ToTaskKey(task.taskType, TaskStatus.doing), totalProgress);
            sumProgressMap.Add(HotUpdateTask.ToTaskKey(task.taskType, TaskStatus.failed), totalProgress);
            
            totalProgress += (System.Int32)statusProgressMap[task.taskType];
            sumProgressMap.Add(HotUpdateTask.ToTaskKey(task.taskType, TaskStatus.done), totalProgress);
        }
    }
    

    protected bool UpdateDownloadProgress(out float percent, out long total, out long current, out float downloadSpeed)
    {
        var task = GetCurTask();

        if(task != null)
        {
            var key = task.ToKey();

            float statusPercent = task.GetProgress(out total, out current);
            if(statusPercent >= 0)
            {
                percent = ((System.Int32)sumProgressMap[key] + statusPercent * (System.Int32)statusProgressMap[task.taskType]) / (float)totalProgress;
                if(!statusDownloadSpeedCheckLastTimeMap.Contains(key))
                {
                    statusDownloadSpeedCheckLastTimeMap.Add(key, Time.time);
                    statusDownloadProgressCheckLastTimeMap.Add(key, 0L);
                    statusDownloadSpeedMap.Add(key, 0.0f);
                }

                float checkTimeInterval = 1;
                float delta = Time.time - (float)statusDownloadSpeedCheckLastTimeMap[key];
                if(delta >= checkTimeInterval)
                {
                    System.Int64 oldProgress = (System.Int64)statusDownloadProgressCheckLastTimeMap[key];
                    statusDownloadSpeedMap[key] = (current - oldProgress) / delta;
                    statusDownloadProgressCheckLastTimeMap[key] = current;
                    statusDownloadSpeedCheckLastTimeMap[key] = Time.time;
                }

                downloadSpeed = (float)statusDownloadSpeedMap[key];
                return true;
            }
        }

        downloadSpeed = 0;
        total = 0;
        current = 0;
        percent = 0;
        return false;
    }



    

    public void MoveNextTaskIfDone()
    {
        var task = GetCurTask();
        if (task != null && task.status == TaskStatus.done)
        {
            curTaskIndex++;
        }
    }

    protected IEnumerator ProcessHotUpdate()
    {
        var task = GetCurTask();

        if (task != null && task.status != TaskStatus.done)
        {
            task.status = TaskStatus.doing;
            var co = task.Run().WithResult();
            yield return co;
            var result = (bool)co.Current;
            if (result)
            {
                task.status = TaskStatus.done;
                yield return new object[] { false, true };
            }
            else
            {
                task.status = TaskStatus.failed;
                yield return new object[] { false, false };
            }
        }
        else
        {
            yield return new object[] { true, true };
        }
    }



    protected bool LoadMetadataForAOTAssembly(byte[] data)
    {
#if !UNITY_WEBGL && ENABLE_IL2CPP && !DISABLE_HYBRIDCLR
        HybridCLR.LoadImageErrorCode code = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(data, HybridCLR.HomologousImageMode.SuperSet);
        if(code != HybridCLR.LoadImageErrorCode.OK)
        {
            return false;
        }
#endif
        return true;
    }

    protected void InitGUI_Size()
    {
        if(Application.isMobilePlatform && !Application.isEditor)
        {
            GUI.skin.label.fontSize = 40;
            GUI.skin.button.fontSize = 40;
            GUI.skin.verticalScrollbar.fixedWidth = 80;
            GUI.skin.verticalScrollbarThumb.fixedWidth = 80;
            GUI.skin.verticalScrollbar.fixedHeight = 80;
            GUI.skin.verticalScrollbarThumb.fixedHeight = 80;
            GUI.skin.button.fixedHeight = 80;
        }
        else
        {
            int scale = 1;
            GUI.skin.label.fontSize = 40 * scale;
            GUI.skin.button.fontSize = 40 * scale;
            GUI.skin.verticalScrollbar.fixedWidth = 80 * scale;
            GUI.skin.verticalScrollbarThumb.fixedWidth = 80 * scale;
            GUI.skin.verticalScrollbar.fixedHeight = 80 * scale;
            GUI.skin.verticalScrollbarThumb.fixedHeight = 80 * scale;
            GUI.skin.button.fixedHeight = 80 * scale;
        }
    }
    

    protected void ShowStaticInfo()
    {
        GUILayout.BeginVertical();
        GUILayout.Label("Time.time: " + Time.time);
        GUILayout.Label("deviceUniqueIdentifier: " + UnityEngine.SystemInfo.deviceUniqueIdentifier);
        GUILayout.Label("hotupdate_version: " + AppManager.GetAppInfo().hotupdate_version);
        GUILayout.Label("compile_type: " + AppManager.GetAppInfo().publish_type);
        GUILayout.Label("branch_name: " + AppManager.GetAppInfo().branch_name);
        GUILayout.Label("repository_version: " + AppManager.GetAppInfo().repository_version);
        GUILayout.Label("timestamp: " + AppManager.GetAppInfo().timestamp);
        GUILayout.Label("datetime: " + AppManager.GetAppInfo().datetime);
        GUILayout.Label("platform: " + PlatformEx.getPlatform());
        GUILayout.Label("InternetReachability: " + Application.internetReachability);
        GUILayout.Label("WhiteDeviceMgr status: " + WhiteDeviceMgr.GetStatus());
        GUILayout.Label("HotUpdateDllMgr status: " + HotUpdateDllMgr.GetStatus());

        long totalBytes = 0;
        long downloadedBytes = 0;
        HotUpdateAddressableMgr.GetProgress(out totalBytes, out downloadedBytes);
        float progress = 0;
        if(totalBytes != 0)
        {
            progress = ((float)downloadedBytes / totalBytes);
        }

        GUILayout.Label("HotUpdateAddressableMgr status: " + HotUpdateAddressableMgr.GetStatus() + " | " + HotUpdateAddressableMgr.GetNeedDownloadSize() + " | " + totalBytes + " | " + downloadedBytes + " | " + progress + " | " + HotUpdateAddressableMgr.GetPercentComplete());
        
        GUILayout.EndVertical();
    }

    protected void ShowSample()
    {
        GUILayout.BeginVertical();

        if (AppManager.GetAppInfo().enable_hotupdate)
        {
            GUILayout.Label("WhiteDeviceMgr:");
            if (!WhiteDeviceMgr.isWhite())
            {
                if (GUILayout.Button("check whtelist"))
                {
                    StartCoroutine(WhiteDeviceMgr.Check(AppManager.GetAppInfo().hotupdate_url, GetHotUpdateDeviceId()).WithResult());
                }
            }
            GUILayout.Label(WhiteDeviceMgr.ToInfoString());
        }

        GUILayout.EndVertical();


        if (AppManager.GetAppInfo().enable_hotupdate)
        {
            GUILayout.BeginVertical();
            GUILayout.Label("HotUpdateDllMgr:");
            switch (HotUpdateDllMgr.GetStatus())
            {
                case HotUpdateDllMgr.Status.none:
                case HotUpdateDllMgr.Status.init_failed:
                    {
                        if (GUILayout.Button("init"))
                        {
                            StartCoroutine(HotUpdateDllMgr.init(AppManager.GetAppInfo().hotupdate_url).WithResult());
                        }
                    }
                    break;

                case HotUpdateDllMgr.Status.inited:
                case HotUpdateDllMgr.Status.version_check_failed:
                    {
                        if (GUILayout.Button("check version"))
                        {
                            StartCoroutine(HotUpdateDllMgr.CheckLatestVersion().WithResult());
                        }
                    }
                    break;

                case HotUpdateDllMgr.Status.version_checked:
                case HotUpdateDllMgr.Status.bundle_download_failed:
                    {
                        if (GUILayout.Button("download"))
                        {
                            StartCoroutine(HotUpdateDllMgr.Download().WithResult());
                        }
                    }
                    break;
                case HotUpdateDllMgr.Status.bundle_downladed:
                case HotUpdateDllMgr.Status.code_load_failed:
                    {
                        if (GUILayout.Button("load code"))
                        {
                            //need fix this code
                            StartCoroutine(HotUpdateDllMgr.LoadCode(new string[] { "test_dll" }, true).WithResult());
                        }
                    }
                    break;

                case HotUpdateDllMgr.Status.code_loaded:
                    {

                    }
                    break;
            }

            GUILayout.Label(HotUpdateDllMgr.ToInfoString());
            GUILayout.Label("");

            GUILayout.EndVertical();


        }






        {
            GUILayout.BeginVertical();
            GUILayout.Label("HotUpdateAddressableMgr:");
            switch (HotUpdateAddressableMgr.GetStatus())
            {
                case HotUpdateAddressableMgr.Status.none:
                case HotUpdateAddressableMgr.Status.init_failed:
                    {
                        if (GUILayout.Button("init"))
                        {
                            int version = Mathf.Max(AppManager.GetAppInfo().hotupdate_version, HotUpdateDllMgr.GetLatestVersion());
                            StartCoroutine(HotUpdateAddressableMgr.init(AppManager.GetAppInfo().hotupdate_url, version).WithResult());
                        }
                    }
                    break;

                case HotUpdateAddressableMgr.Status.inited:
                case HotUpdateAddressableMgr.Status.version_check_failed:
                    {
                        if (GUILayout.Button("check version"))
                        {
                            StartCoroutine(HotUpdateAddressableMgr.CheckLatestVersion().WithResult());
                        }
                    }
                    break;
                case HotUpdateAddressableMgr.Status.version_checked:
                case HotUpdateAddressableMgr.Status.catalog_update_failed:
                    {
                        if (GUILayout.Button("update catalog"))
                        {
                            StartCoroutine(HotUpdateAddressableMgr.Update().WithResult());
                        }
                    }
                    break;
                case HotUpdateAddressableMgr.Status.catalog_updated:
                case HotUpdateAddressableMgr.Status.download_failed:
                    {
                        if (GUILayout.Button("download"))
                        {
                            StartCoroutine(HotUpdateAddressableMgr.Download().WithResult());
                        }
                    }
                    break;

            }


            GUILayout.Label(HotUpdateAddressableMgr.ToInfoString());
            GUILayout.Label("");

            GUILayout.EndVertical();
        }
    }
}
